elk_jquery_embed.js ➔ scrollEmbed   F
last analyzed

Complexity

Conditions 44

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 44
eloc 30
dl 0
loc 53
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like elk_jquery_embed.js ➔ scrollEmbed often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * @package   ElkArte Forum
3
 * @copyright ElkArte Forum contributors
4
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
5
 *
6
 * @version 2.0 Beta 1
7
 *
8
 * Original code from Aziz, redone and refactored for ElkArte
9
 */
10
11
/** global: elk_session_id, elk_session_var, elk_scripturl */
12
13
/**
14
 * This JavaScript searches the message for video links and replaces them
15
 * with a clickable preview thumbnail of the video.  Once the image is clicked
16
 * the video is embedded in to the page to play.
17
 *
18
 * Currently, works with YouTube, Vimeo, TikTok, Twitter, Facebook, Instagram and DailyMotion
19
 *
20
 */
21
(function($) {
22
	'use strict';
23
24
	/**
25
	 * @param {object} oInstanceSettings holds the text strings to use in the html created
26
	 * @param {int} msgid optional to only search for links in a specific id
27
	 */
28
	$.fn.linkifyvideo = function(oInstanceSettings, msgid) {
29
		let oDefaultsSettings = {
30
			embed_limit: 25,
31
			preview_image: '',
32
			ctp_video: '',
33
			hide_video: '',
34
			youtube: '',
35
			vimeo: '',
36
			dailymotion: '',
37
			tiktok: '',
38
			twitter: '',
39
			facebook: '',
40
			instagram: ''
41
		};
42
43
		// Account for user options
44
		let oSettings = $.extend({}, oDefaultsSettings, oInstanceSettings || {});
45
46
		/**
47
		 * Replaces the image with the created embed code to show the video
48
		 * Called from click event attached to the image
49
		 *
50
		 * @param {string} tag anchor tag we are replacing with the embed tag
51
		 * @param {string} eURL the load or place source link
52
		 * @param {boolean} bAspect if to use a tall vs wide
53
		 */
54
		function showEmbed (tag, eURL, bAspect)
55
		{
56
			if (bAspect)
57
			{
58
				$(tag).html(embed_html.replace('{src}', eURL));
0 ignored issues
show
Bug introduced by
The local (let) variable embed_html is used before it is defined. This will cause a reference error.
Loading history...
59
			}
60
			else
61
			{
62
				$(tag).html(embed_html_916.replace('{src}', eURL));
0 ignored issues
show
Bug introduced by
The local (let) variable embed_html_916 is used before it is defined. This will cause a reference error.
Loading history...
63
			}
64
		}
65
66
		/**
67
		 * Shows the video image and sets up the link
68
		 * Sets click event to load video sites embed code
69
		 *
70
		 * @param {object} a videoID link
71
		 * @param {string} src source of image
72
		 * @param {string} eURLa play link
73
		 * @param {boolean} bAspect false to use a 9/16 iframe vs 16x9
74
		 */
75
		function getIMG (a, src, eURLa, bAspect)
76
		{
77
			return $('' +
78
				'<div class="elk_video">' +
79
				'   <a href="' + a.href + '">' +
80
				'       <img class="elk_video_preview" alt="' + oSettings.preview_image + '" ' + 'title="' + oSettings.ctp_video + '" src="' + src + '"/>' +
81
				'   </a>' +
82
				'</div>')
83
				.on('click', function(e) {
84
						e.preventDefault();
85
						let tag = this;
86
						showEmbed(tag, eURLa, bAspect);
87
					}
88
				);
89
		}
90
91
		/**
92
		 * Returns a linked preview image.  Click on the image to load the player.
93
		 *
94
		 * @param {string} a link tag of the video
95
		 * @param {string} src link of the preview image
96
		 * @param {string} eURLa single click event play video
97
		 * @param {boolean} bAspect use a wide vs tall ratio
98
		 */
99
		function embedIMG (a, src, eURLa, bAspect = true)
100
		{
101
			return getIMG(a, src, eURLa, bAspect);
102
		}
103
104
		/**
105
		 * Creates and inserts a document fragment.  Doing this vs inner/outer HTML ensures that any script
106
		 * tags in the embed code will execute.
107
		 *
108
		 * @param {Element} a the link we are working with
109
		 * @param {object} data the data from the ajax call
110
		 */
111
		function createFragment (a, data)
112
		{
113
			// Since data.html may contain a script tag that needs to run, we have to add it like this
114
			let parent = a.parentNode,
115
				frag = document.createRange().createContextualFragment('<div class="elk_video">' + data.html + '</div>');
116
117
			parent.parentNode.appendChild(frag);
118
			parent.nextSibling.outerHTML = '';
119
		}
120
121
		/**
122
		 * Converts a given time value from an array to seconds using a multiplier.
123
		 *
124
		 * @param {Array} timeArray - The array containing time values.
125
		 * @param {number} timeIndex - The index of the time value to convert.
126
		 * @param {number} multiplier - The multiplier to apply to the converted time value.
127
		 * @return {number} - The converted time value in seconds, or 0 if the time array is undefined or the timeIndex is out of range.
128
		 */
129
		function convertToSeconds (timeArray, timeIndex, multiplier)
130
		{
131
			if (typeof timeArray[timeIndex] !== 'undefined')
132
			{
133
				return parseInt(timeArray[timeIndex]) * multiplier;
134
			}
135
136
			return 0;
137
		}
138
139
		// The embed code
140
		let domain_regex = /^[^:]*:\/\/(?:www\.)?([^\/]+)(\/.*)$/,
141
			embedded_count = 0,
142
			provider_class = '',
143
			embed_html = '<iframe width="640" height="360" src="{src}" data-autoplay="true" allow="fullscreen" loading="lazy" type="text/html"></iframe>',
0 ignored issues
show
Unused Code introduced by
The variable embed_html seems to be never used. Consider removing it.
Loading history...
144
			embed_html_916 = '<iframe width="480" height="800" src="{src}" allow="fullscreen" loading="lazy" type="text/html"></iframe>',
0 ignored issues
show
Unused Code introduced by
The variable embed_html_916 seems to be never used. Consider removing it.
Loading history...
145
			handlers = {},
146
			imgHandlers = {},
147
			logos = {
148
				tiktok: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%23fff\' fill-opacity=\'.01\' d=\'M0 0h48v48H0z\'/%3E%3Cpath fill=\'%232F88FF\' stroke=\'%23000\' stroke-linejoin=\'round\' stroke-width=\'3.833\' d=\'M21.358 19.14c-5.888-.284-9.982 1.815-12.28 6.299-3.446 6.724-.597 17.728 10.901 17.728 11.499 0 11.831-11.111 11.831-12.276V17.876c2.46 1.557 4.533 2.495 6.221 2.813 1.688.317 2.76.458 3.219.422v-6.476c-1.561-.188-2.911-.547-4.05-1.076-1.709-.794-5.096-2.997-5.096-6.226.002.016.002-.817 0-2.499h-7.118c-.021 15.816-.021 24.502 0 26.058.032 2.334-1.779 5.6-5.45 5.6-3.672 0-5.482-3.263-5.482-5.367 0-1.288.442-3.155 2.271-4.538 1.085-.82 2.59-1.147 5.033-1.147V19.14Z\'/%3E%3C/svg%3E',
149
				vimeo: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 455.731 455.731\'%3E%3Cpath fill=\'%231ab7ea\' d=\'M0 0h455.731v455.731H0z\'/%3E%3Cpath fill=\'%23fff\' d=\'m49.642 157.084 17.626 22.474s22.033-17.186 29.965-17.186c4.927 0 15.423 5.729 22.033 25.558 6.61 19.83 34.441 122.62 36.134 127.351 7.607 21.26 17.626 60.811 48.473 66.54s70.065-25.558 91.657-48.473c21.592-22.914 106.64-120.741 110.165-179.349 3.26-54.191-14.517-66.765-22.474-71.828-14.542-9.254-38.778-12.338-61.692-4.407s-57.726 33.931-66.98 80.2c0 0 31.287-11.457 42.744-.441s8.373 35.253-1.322 53.32-37.015 59.93-47.151 61.252c-10.135 1.322-18.067-18.508-19.389-23.796-1.322-5.288-18.067-77.997-24.236-120.3s-33.049-49.354-45.829-49.354c-12.779.001-34.812 9.696-109.724 78.439z\'/%3E%3C/svg%3E',
150
				dailymotion: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 512 512\'%3E%3Cpath fill=\'%230066DC\' fill-rule=\'evenodd\' d=\'M0 512h512V0H0v512Zm441.5-68.635h-76.314v-29.928c-23.443 22.945-47.385 31.424-79.308 31.424-32.421 0-60.354-10.474-83.797-31.424-30.926-27.433-46.887-63.346-46.887-105.245 0-38.407 14.965-72.823 42.896-99.758 24.94-24.44 55.367-36.91 89.284-36.91 32.422 0 57.361 10.973 75.318 33.917V88.724L441.5 72.395v370.97Zm-141.157-202.01c-37.41 0-66.339 30.426-66.339 66.338 0 37.41 28.93 65.841 69.332 65.841 33.918 0 62.349-27.932 62.349-64.843 0-38.406-28.431-67.336-65.342-67.336Z\'/%3E%3C/svg%3E',
151
				twitter: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 512 512\'%3E%3Cpath d=\'M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z\'/%3E%3C/svg%3E',
152
				facebook: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 128 128\'%3E%3Cpath fill=\'%233B5998\' d=\'M126 118a8 8 0 0 1-8 8H10a8 8 0 0 1-8-8V10a8 8 0 0 1 8-8h108a8 8 0 0 1 8 8v108z\'/%3E%3Cpath fill=\'%236D84B4\' d=\'M5.667 98.98h116.666v18.039H5.667z\'/%3E%3Cpath fill=\'%23FFF\' d=\'M93.376 117.012H72.203V65.767H61.625v-17.66h10.578V37.504c0-14.407 5.973-22.974 22.943-22.974h14.128v17.662h-8.831c-6.606 0-7.043 2.468-7.043 7.074l-.024 8.839h15.998l-1.872 17.66H93.376v51.247z\'/%3E%3C/svg%3E',
153
				instagram: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 256 256\'%3E%3Cpath fill=\'%230A0A08\' d=\'M128 23.064c34.177 0 38.225.13 51.722.745 12.48.57 19.258 2.655 23.769 4.408 5.974 2.322 10.238 5.096 14.717 9.575 4.48 4.479 7.253 8.743 9.575 14.717 1.753 4.511 3.838 11.289 4.408 23.768.615 13.498.745 17.546.745 51.723 0 34.178-.13 38.226-.745 51.723-.57 12.48-2.655 19.257-4.408 23.768-2.322 5.974-5.096 10.239-9.575 14.718-4.479 4.479-8.743 7.253-14.717 9.574-4.511 1.753-11.289 3.839-23.769 4.408-13.495.616-17.543.746-51.722.746-34.18 0-38.228-.13-51.723-.746-12.48-.57-19.257-2.655-23.768-4.408-5.974-2.321-10.239-5.095-14.718-9.574-4.479-4.48-7.253-8.744-9.574-14.718-1.753-4.51-3.839-11.288-4.408-23.768-.616-13.497-.746-17.545-.746-51.723 0-34.177.13-38.225.746-51.722.57-12.48 2.655-19.258 4.408-23.769 2.321-5.974 5.095-10.238 9.574-14.717 4.48-4.48 8.744-7.253 14.718-9.575 4.51-1.753 11.288-3.838 23.768-4.408 13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95-8.418 3.271-15.556 7.648-22.672 14.764C14.367 28.6 9.991 35.738 6.72 44.155 3.555 52.297 1.392 61.602.77 75.226.147 88.878 0 93.237 0 128c0 34.763.147 39.122.77 52.774.622 13.625 2.785 22.93 5.95 31.071 3.27 8.417 7.647 15.556 14.763 22.672 7.116 7.116 14.254 11.492 22.672 14.763 8.142 3.165 17.446 5.328 31.07 5.95 13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95 8.418-3.27 15.556-7.647 22.672-14.763 7.116-7.116 11.493-14.254 14.764-22.672 3.164-8.142 5.328-17.446 5.95-31.07.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07-3.271-8.418-7.648-15.556-14.764-22.672C227.4 14.368 220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0Zm0 62.27C91.698 62.27 62.27 91.7 62.27 128c0 36.302 29.428 65.73 65.73 65.73 36.301 0 65.73-29.428 65.73-65.73 0-36.301-29.429-65.73-65.73-65.73Zm0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667-19.103 42.667-42.667 42.667Zm83.686-110.994c0 8.484-6.876 15.36-15.36 15.36-8.483 0-15.36-6.876-15.36-15.36 0-8.483 6.877-15.36 15.36-15.36 8.484 0 15.36 6.877 15.36 15.36Z\'/%3E%3C/svg%3E',
154
				youtube: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'-100 -100 661 661\'%3E%3Cpath d=\'M365.257 67.393H95.744C42.866 67.393 0 110.259 0 163.137v134.728c0 52.878 42.866 95.744 95.744 95.744h269.513c52.878 0 95.744-42.866 95.744-95.744V163.137c0-52.878-42.866-95.744-95.744-95.744zm-64.751 169.663-126.06 60.123c-3.359 1.602-7.239-.847-7.239-4.568V168.607c0-3.774 3.982-6.22 7.348-4.514l126.06 63.881c3.748 1.899 3.683 7.274-.109 9.082z\' style=\'fill:%23f61c0d\'/%3E%3C/svg%3E',
155
			};
156
157
		// Get a YouTube video thumbnail
158
		imgHandlers.getYoutubeIMG = function(eURL, callback) {
159
			fetchDocument(eURL, ytResponse, 'json');
160
161
			function ytResponse (data)
162
			{
163
				if (!data || typeof data.thumbnail_url === 'undefined')
164
				{
165
					callback(logos.youtube);
166
				}
167
				else
168
				{
169
					callback(data.thumbnail_url);
170
				}
171
			}
172
		};
173
174
		// Get a twitter embed html
175
		imgHandlers.getTwitterEmbed = function(eURL, callback) {
176
			fetchDocument(eURL, twResponse, 'json');
177
178
			function twResponse (data)
179
			{
180
				if (!data || typeof data.html === 'undefined')
181
				{
182
					data.html = '';
183
				}
184
185
				callback(data);
186
			}
187
		};
188
189
		// Get a TikTok video thumbnail and embed data
190
		imgHandlers.getTikTokEmbed = function(eURL, callback) {
191
			fetchDocument(eURL, ttResponse, 'json');
192
193
			function ttResponse (data)
194
			{
195
				if (!data || typeof data.html === 'undefined')
196
				{
197
					data.thumbnail_url = logos.tiktok;
198
					data.html = '';
199
				}
200
201
				callback(data);
202
			}
203
		};
204
205
		// Get a Facebook video thumbnail // will not work without api codes
206
		imgHandlers.getFacebookEmbed = function(eURL, callback) {
207
			fetchDocument(eURL, fbResponse, 'json');
208
209
			function fbResponse (data)
210
			{
211
				if (!data || typeof data.thumbnail_url === 'undefined')
212
				{
213
					callback(logos.facebook);
214
				}
215
				else
216
				{
217
					callback(data.thumbnail_url);
218
				}
219
			}
220
		};
221
222
		// Get an Instagram video thumbnail // will not work without api codes
223
		imgHandlers.getInstagramEmbed = function(eURL, callback) {
224
			fetchDocument(eURL, instaResponse, 'json');
225
226
			function instaResponse (data)
227
			{
228
				if (!data || typeof data.thumbnail_url === 'undefined')
229
				{
230
					callback(logos.instagram);
231
				}
232
				else
233
				{
234
					callback(data.thumbnail_url);
235
				}
236
			}
237
		};
238
239
		// Get a dailymotion video thumbnail
240
		imgHandlers.getDailymotionIMG = function(eURL, callback) {
241
			fetchDocument(eURL, dailyResponse, 'json', false);
242
243
			function dailyResponse (data)
244
			{
245
				if (!data || typeof data.thumbnail_480_url === 'undefined')
246
				{
247
					callback(logos.dailymotion);
248
				}
249
				else
250
				{
251
					callback(data.thumbnail_480_url);
252
				}
253
			}
254
		};
255
256
		// Get a Vimeo video thumbnail
257
		imgHandlers.getVimeoIMG = function(eURL, callback) {
258
			fetchDocument(eURL, vimeoResponse, 'json', false);
259
260
			function vimeoResponse (data)
261
			{
262
				if (!data || typeof data[0].thumbnail_large === 'undefined')
263
				{
264
					callback(logos.vimeo);
265
				}
266
				else
267
				{
268
					callback(data[0].thumbnail_large);
269
				}
270
			}
271
		};
272
273
		// Youtube and variants
274
		handlers['youtube.com'] = function(path, a) {
275
			let videoID = path.match(/\bv[=/]([^&#?$]+)/i) || path.match(/#p\/(?:a\/)?[uf]\/\d+\/([^?$]+)/i) || path.match(/(?:\/)([\w-]{11})/i);
276
277
			if (!videoID || !(videoID = videoID[1]))
278
			{
279
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
280
			}
281
282
			// There are two types of YouTube timestamped links
283
			// http://youtu.be/lLOE3fBZcUU?t=1m37s when you click share underneath the video
284
			// http://youtu.be/lLOE3fBZcUU?t=97 when you right-click on a video and choose "Copy video URL at current time"
285
			// For embedding, you need to use "?start=97" instead, so we have to convert t=1m37s to seconds while also supporting t=97
286
			let startAt = path.match(/t=(?:([1-9]{1,2})h)?(?:([1-9]{1,2})m)?(?:([1-9]+)s?)/),
287
				startAtPar = '';
288
289
			if (startAt)
290
			{
291
				let startAtSeconds = 0;
292
293
				startAtSeconds += convertToSeconds(startAt, 1, 3600);  // Hours
294
				startAtSeconds += convertToSeconds(startAt, 2, 60);    // Minutes
295
				startAtSeconds += convertToSeconds(startAt, 3, 1);     // Seconds
296
297
				startAtPar = '&start=' + startAtSeconds.toString();
298
			}
299
300
			let embedURL = '//www.youtube-nocookie.com/embed/' + videoID + '?rel=0' + startAtPar,
301
				imgURL = '//www.youtube.com/oembed?url=https://www.youtube.com/watch?v=' + videoID + '&format=json',
302
				tag = embedIMG(a, '//i.ytimg.com/vi/' + videoID + '/sddefault.jpg', embedURL + '&autoplay=1');
303
304
			// Get the preview image / embed tag
305
			imgHandlers.getYoutubeIMG(imgURL, function(img) {
306
				$(a).parent().next().find('img').attr('src', img);
307
			});
308
309
			return [oSettings.youtube, tag];
310
		};
311
		handlers['m.youtube.com'] = handlers['youtube.com'];
312
		handlers['youtu.be'] = handlers['youtube.com'];
313
314
		// Vimeo
315
		handlers['vimeo.com'] = function(path, a) {
316
			let videoID = path.match(/^\/(\d+)/i);
317
318
			if (!videoID || !(videoID = videoID[1]))
319
			{
320
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
321
			}
322
323
			let embedURL = '//player.vimeo.com/video/' + videoID,
324
				imgURL = '//vimeo.com/api/v2/video/' + videoID + '.json',
325
				tag;
326
327
			tag = embedIMG(a, logos.vimeo, embedURL + '?autoplay=1');
328
329
			// Get the preview image / embed tag
330
			imgHandlers.getVimeoIMG(imgURL, function(img) {
331
				$(a).parent().next().find('img').attr('src', img);
332
			});
333
334
			return [oSettings.vimeo, tag];
335
		};
336
337
		// Dailymotion
338
		handlers['dailymotion.com'] = function(path, a) {
339
			let videoID = path.match(/^\/video\/([a-z0-9]{1,18})/i);
340
341
			if (!videoID || videoID[1] === '')
342
			{
343
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
344
			}
345
346
			let embedURL = '//dailymotion.com/embed/video/' + videoID[1],
347
				imgURL = '//api.dailymotion.com/video/' + videoID[1] + '?fields=thumbnail_480_url',
348
				tag;
349
350
			tag = embedIMG(a, logos.dailymotion, embedURL + '?related=0&autoplay=1');
351
352
			// Get the preview image or embed tag
353
			imgHandlers.getDailymotionIMG(imgURL, function(img) {
354
				$(a).parent().next().find('img').attr('src', img);
355
			});
356
357
			return [oSettings.dailymotion, tag];
358
		};
359
360
		// TikTok
361
		handlers['tiktok.com'] = function(path, a) {
362
			let videoID = path.match(/^\/@([0-9A-Za-z_\-.]*)\/video\/([0-9]*)/i);
363
364
			if (!videoID)
365
			{
366
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
367
			}
368
369
			let videoURL = 'https://www.tiktok.com/@' + videoID[1] + '/video/' + videoID[2],
370
				embedURL = elk_prepareScriptUrl(elk_scripturl) + 'action=xmlhttp;sa=videoembed;api=json;site=tiktok;videoid=' + videoURL + ';' + elk_session_var + '=' + elk_session_id,
371
				tag;
372
373
			imgHandlers.getTikTokEmbed(embedURL, function(data) {
374
				if (typeof data.thumbnail_url !== 'undefined')
375
				{
376
					$(a).parent().next().find('img').attr('src', data.thumbnail_url);
377
				}
378
				a.embedURL = data.html;
379
			});
380
381
			tag = embedIMG(a, logos.tiktok, embedURL);
382
383
			// Change the default click event to one that replaces the markup
384
			tag.off('click', '**', false);
385
			tag.on('click', function(e) {
386
				e.preventDefault();
387
				let load = $(a).parent();
388
				load.parent().addClass('portrait');
389
390
				load.next().replaceWith('<div class="elk_video">' + a.embedURL + '</div>');
391
			});
392
393
			return [oSettings.tiktok, tag];
394
		};
395
396
		// Twitter
397
		handlers['twitter.com'] = function(path, a) {
398
			let videoID = path.match(/\/status\/([0-9]{16,20})/);
399
400
			if (!videoID || videoID[1] === '')
401
			{
402
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
403
			}
404
405
			let embedURL = elk_prepareScriptUrl(elk_scripturl) + 'action=xmlhttp;sa=videoembed;api=json;site=twitter;videoid=' + videoID[1] + ';' + elk_session_var + '=' + elk_session_id,
406
				tag;
407
408
			tag = embedIMG(a, logos.twitter, embedURL);
409
410
			// Twitter has its own embed codes we need to load, no preview here, replace click event as well
411
			tag.off('click', '**', false);
412
			a.embedURL = embedURL;
413
			a.setAttribute('data-video_embed', 'getTwitterEmbed');
414
415
			return [oSettings.twitter, tag, 'portrait'];
416
		};
417
		handlers['x.com'] = handlers['twitter.com'];
418
419
		// Facebook
420
		handlers['facebook.com'] = function(path, a) {
421
			let videoID = path.match(/([\d\w._-]+)?(?:\/videos\/|\/video.php\?v=)(\d+)/i);
422
423
			if (!videoID || videoID[1] === '' || videoID[2] === '')
424
			{
425
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
426
			}
427
428
			let embedURL = '//www.facebook.com/plugins/video.php?href=https://www.facebook.com/' + videoID[1] + '/videos/' + videoID[2],
429
				tag;
430
431
			tag = embedIMG(a, logos.facebook, embedURL + '?related=0&autoplay=1');
432
433
			return [oSettings.facebook, tag];
434
		};
435
436
		// Instagram
437
		handlers['instagram.com'] = function(path, a) {
438
			let videoID = path.match(/\/(?:tv|p)\/([a-z0-9]{10,18})(?:\/\?|\/)?/i);
439
440
			if (!videoID || videoID[1] === '')
441
			{
442
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
443
			}
444
445
			let embedURL = '//www.instagram.com/p/' + videoID[1] + '/embed',
446
				tag;
447
448
			tag = embedIMG(a, logos.instagram, embedURL + '?related=0&autoplay=1', false);
449
450
			return [oSettings.instagram, tag, 'portrait'];
451
		};
452
453
		// ---------------------------------------------------------------------------
454
		// Get the bbc_link links in the id="msg_1234 divs.
455
		let links;
456
457
		if (typeof msgid !== 'undefined')
458
		{
459
			links = document.querySelectorAll('#' + msgid + ' a.bbc_link');
460
		}
461
		else
462
		{
463
			links = document.querySelectorAll('[id^=msg_] a.bbc_link');
464
		}
465
466
		// Create the show/hide button
467
		let showhideBtn = $('' +
468
			'<a class="floatright" title="' + oSettings.hide_video + '">' +
469
			'   <i class="icon icon-small i-caret-up" alt=">"></i>' +
470
			'</a>')
471
			.on('click', function() {
472
				let $img = $(this).find('i'), // The open / close icon
473
					$vid = $(this).parent().next(); // The immediate elk_video div
474
475
				// Toggle slide the video and change the icon
476
				$img.attr('class', 'icon icon-small ' + ($vid.is(':hidden') !== true ? 'i-caret-down' : 'i-caret-up'));
477
				$vid.slideToggle();
478
			});
479
480
		// Loop though each link
481
		links.forEach((link) => {
482
			let tag = link,
483
				text = tag.innerText || tag.textContent || '';
484
485
			// Already processed?
486
			if (tag.classList.contains('elk_video_processed'))
487
			{
488
				return;
489
			}
490
491
			// Ignore in sentences
492
			if (tag.previousSibling && tag.previousSibling.nodeName === '#text' && tag.previousSibling.nodeValue !== ' ')
493
			{
494
				return;
495
			}
496
497
			// Ignore in quotes and signatures
498
			if ('bbc_quote;signature'.indexOf(tag.parentNode.className) !== -1)
499
			{
500
				return;
501
			}
502
503
			// No href or inner text not equal to href attr then we move along
504
			if (tag.href === '' || tag.href.indexOf(text) !== 0)
505
			{
506
				return;
507
			}
508
509
			// Get domain and validate we know how to handle it
510
			let m = tag.href.match(domain_regex),
511
				handler = null,
0 ignored issues
show
Unused Code introduced by
The assignment to handler seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
512
				args = null;
0 ignored issues
show
Unused Code introduced by
The assignment to args seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
513
514
			// One of our video provider domains?
515
			if (embedded_count < oSettings.embed_limit && m !== null && typeof handlers[m[1]] !== 'undefined' && handlers[m[1]] !== null)
516
			{
517
				// Call the handler and get the tag to insert
518
				handler = handlers[m[1]];
519
520
				// If there are video tags seperated by only a BR node, remove the BR so the video embed can
521
				// be side by side on a wide enough screen.
522
				if (tag.previousSibling && tag.previousSibling.nodeName === 'BR')
523
				{
524
					if (tag.previousSibling.previousElementSibling && tag.previousSibling.previousElementSibling.classList.contains('elk_video_container'))
525
					{
526
						tag.previousSibling.remove();
527
					}
528
				}
529
530
				args = handler(m[2], tag, provider_class);
531
				if (args)
532
				{
533
					tag.classList.add('elk_video_processed');
534
					embedded_count++;
535
					$(tag).wrap('<div class="elk_video_container ' + (typeof args[2] !== 'undefined' ? args[2] : '') + '">');
536
					$(tag).wrap('<div class="elk_video_header">').text(args[0]).after(showhideBtn.clone(true));
537
					$(tag).parent().parent().append(args[1]);
538
				}
539
			}
540
		});
541
542
		// If we have embedded videos, add the lazy load code and events
543
		if (embedded_count > 0)
544
		{
545
			scrollEmbed();
546
		}
547
548
		/**
549
		 * Some sites have no thumbnail, so we mimic an onclick to load the embed when the element is on screen.  This
550
		 * provides something other than the default logo.
551
		 *
552
		 * Note: This does now work for all sites, like instagram, due to cors errors.  For those you need to set the onclick
553
		 * and let the user load the embed.
554
		 */
555
		function scrollEmbed ()
556
		{
557
			let videoLinks = document.querySelectorAll('a[data-video_embed]'),
558
				throttleTimeout,
559
				found = false;
560
561
			/**
562
			 * Function that fires to lazy load video sites embed when in viewport
563
			 */
564
			function videoLinksListener ()
565
			{
566
				if (throttleTimeout)
567
				{
568
					clearTimeout(throttleTimeout);
569
				}
570
571
				// On scroll fires "a lot" so this tames it to be less abusive
572
				throttleTimeout = setTimeout(function() {
573
					videoLinks.forEach(function(a) {
574
						// No links remaining, drop any listeners
575
						if (videoLinks.length === 0)
576
						{
577
							document.removeEventListener('scroll', videoLinksListener);
578
							window.removeEventListener('resize', videoLinksListener);
579
							window.removeEventListener('orientationChange', videoLinksListener);
580
						}
581
582
						// Hey I see you ...
583
						if (isElementInViewport(a))
584
						{
585
							let func = a.getAttribute('data-video_embed');
586
							found = true;
587
							a.removeAttribute('data-video_embed');
588
							imgHandlers[func](a.embedURL, (data) => {
589
								createFragment(a, data);
590
							});
591
						}
592
					});
593
594
					// Once a link is found, let's not try that one again.
595
					if (found)
596
					{
597
						videoLinks = document.querySelectorAll('a[data-video_embed]');
598
					}
599
				}, 25);
600
			}
601
602
			// Scroll, rotate or resize, we check if we can see the video link.
603
			document.addEventListener('scroll', videoLinksListener);
604
			window.addEventListener('DOMContentLoaded', videoLinksListener);
605
			window.addEventListener('resize', videoLinksListener);
606
			window.addEventListener('orientationChange', videoLinksListener);
607
		}
608
	};
609
})(jQuery);
610